#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Purpose: 
# Created: 11/20/2008
# Copyright (c) 2008,2009 by University College London
# Authors:
# Didrik Pinte <dpinte@dipole-consulting.com>
#
# This program is free software under the GPL (>=v2)
# Read the file COPYING coming with HydroPlatform for details.

import logging
from math import sqrt, pow
import shapely
import shapely.geometry

from Thuban import internal_from_unicode, _
from Thuban.Model.layer import BaseLayer
from hydroplatform.model import network as nt

logging.basicConfig()

class NetworkLayer(BaseLayer):
    """
    Network layer

    This layer incorporates all methods from the Thuban BaseLayer and
    adds specific methods for operating with an hydroplatform network layer
    """

    def __init__(self, network, projection=None):
        """Initializes the NetworkLayer.

        network - a hydroplatform.model.network.Network class

        If an error occured, self.error_msg is a string describing
        the problem(s). Else, self.error_msg is None.
        """
        BaseLayer.__init__(self, network.name, visible = True, projection=projection)
        
        assert isinstance(network, nt.Network)
        self.network = network
        # subscribe to the NETWORK_NODE_ADDED event that will be fired from the
        # network.Network.add_node method
        network.Subscribe(nt.NETWORK_NODE_ADDED, self.OnNodeAdded)
        network.Subscribe(nt.NETWORK_LINK_ADDED, self.OnAddLink)
        
        self.labeled = False
        
        # layers is a dictionnary with key = node type name 
        # and value is the list of nodes of this type for the given network
        self.layers = {}
        self.bbox = None
        if len(self.network.nodes) > 0:
            self.bbox = [1E8,1E8,-1E8,-1E8]
        for node in self.network.nodes:
            self.OnNodeAdded(node)
        
        for link in self.network.links:
            self.OnAddLink(link)
            
        if self.bbox is not None:
            self.latlonbbox = tuple(self.bbox)
            self.bbox = tuple(self.bbox)
        else :
            self.latlonbbox = None
        # check coords
        self.visible_layers = self.layers.keys()
        
        self.SetTitle(self.network.name)


    def OnNodeAdded(self, node):
        '''
        Update bbox and layers
        '''
        if self.bbox is None:
            bbox = [1E8,1E8,-1E8,-1E8]
        else : bbox = list(self.bbox)
        
        if not self.layers.has_key(node.type.name):
            self.layers[node.type.name] = [node]
        else: self.layers[node.type.name].append(node)

        if node.x is None or node.y is None: 
            logging.error("Invalid node - x or y is None : %s" % node)
            return
        else :
            if node.x < bbox[0]: bbox[0] = node.x
            if node.x > bbox[2]: bbox[2] = node.x
            if node.y < bbox[1]: bbox[1] = node.y
            if node.y > bbox[3]: bbox[3] = node.y
        self.bbox = tuple(bbox)
    
    def OnAddLink(self, link):
        # FIXME : should manage the linktypes to be able to render links
        # based on their type
        if not self.layers.has_key(u'link'):
            self.layers[u'link'] = [link]
        else: self.layers[u'link'].append(link)
        
    def HasShapes(self):
        '''
        Overload BaseLayer.HasShapes that default to false
        '''
        if len(self.network.nodes) > 0:  return True
        else: return False
        
    def Type(self):
        return 'Hydroplatform network layer'

    
    def getLayers(self):
        '''
        Returns the list of layers available in the network :
         - one layer per node type
         - one layer per link type
        '''
        return self.layers.keys()

    def getLayerTitle(self, layer):
        """
        Return the title of the named layer

        """
        return layer


    def getVisibleLayers(self):
        """
        Return the list of names for all visible layers

        """
        return self.visible_layers


    def setVisibleLayers(self, layers):
        """
        Set the list of names for all visible layers

        """
        self.visible_layers = layers


        
    def LatLongBoundingBox(self):
        """
        Return the layer's bounding box in lat-lon
        """
        bbox = self.BoundingBox()
        if bbox is not None and self.projection is not None:
            bbox = self.projection.InverseBBox(bbox)
        return bbox


    def BoundingBox(self):
        """
        Return the layer's bounding box in the intrinsic coordinate system
        
        Added a .1 border on the bbox
        
        If the layer has no shape return None
        """
        if self.bbox is None:
            return self.bbox
        
        ratio = .1
        xratio = (self.bbox[2] - self.bbox[0]) * ratio
        yratio = (self.bbox[3] - self.bbox[1]) * ratio
        
        return [self.bbox[0] - xratio, self.bbox[1] - yratio, 
                self.bbox[2] + xratio, self.bbox[3] + yratio] 
    
    def find_closest_node(self, pos):
        """
        Returns the closest node to position (x,y)
        """        
        minnode = None
        mindist = None
        point = shapely.geometry.Point(pos[0], pos[1])
        for node in self.network.nodes:
            distance = node.getGeometry().distance(point)
            if mindist is None: 
                mindist = distance
                minnode = node
            elif distance < mindist: 
                mindist = distance
                minnode = node
        return minnode 
    
    def find_closest_node_in_box(self, box, unselect=False):
        resnode = dict()
        reslink = dict()
        # box must be converted to a list of (x,y) coordinates
        # ((minx, miny), (minx, maxy), (max, maxy))
        coords = [(box[0], box[1]), (box[0], box[3]), (box[2], box[3]), (box[2], box[1])]
        rectangle = shapely.geometry.Polygon(coords)
        # find all the geometries that are inside the rectangle
        for node in self.network.nodes:
            if node.getGeometry().within(rectangle):
                # keep minimal distance to the rectangle for the node
                resnode[node] =node.getGeometry().distance(rectangle)
            if unselect is True:
                node.selected = False
        for link in self.network.links:
            bidirectionnal = self.network.has_link(link.end, link.start)
            if link.getGeometry(bidirectionnal).crosses(rectangle) \
               or link.getGeometry().within(rectangle):
                # keep minimal distance to the rectangle for the node
                reslink[link] = link.getGeometry(bidirectionnal).distance(rectangle)
            if unselect is True:
                link.selected = False                
                
        for res in [resnode, reslink]  :
            keylength = len(res.keys())
            if keylength ==  1:
                return res.keys()[0]
            elif keylength > 1:
                # FIXME : should we put node in priority ?
                # return the one with the minimal distance
                idx = res.values().index(min(res.values()))
                return res.keys()[idx]
        else: 
            return None